home *** CD-ROM | disk | FTP | other *** search
/ SGI Developer Toolbox 6.1 / SGI Developer Toolbox 6.1 - Disc 4.iso / public / bit / src / jpeg / jmemdos.c < prev    next >
C/C++ Source or Header  |  1994-08-01  |  18KB  |  614 lines

  1. /*
  2.  * jmemdos.c  (jmemsys.c)
  3.  *
  4.  * Copyright (C) 1992, Thomas G. Lane.
  5.  * This file is part of the Independent JPEG Group's software.
  6.  * For conditions of distribution and use, see the accompanying README file.
  7.  *
  8.  * This file provides an MS-DOS-compatible implementation of the system-
  9.  * dependent portion of the JPEG memory manager.  Temporary data can be
  10.  * stored in extended or expanded memory as well as in regular DOS files.
  11.  *
  12.  * If you use this file, you must be sure that NEED_FAR_POINTERS is defined
  13.  * if you compile in a small-data memory model; it should NOT be defined if
  14.  * you use a large-data memory model.  This file is not recommended if you
  15.  * are using a flat-memory-space 386 environment such as DJGCC or Watcom C.
  16.  *
  17.  * Based on code contributed by Ge' Weijers.
  18.  */
  19.  
  20. /*
  21.  * If you have both extended and expanded memory, you may want to change the
  22.  * order in which they are tried in jopen_backing_store.  On a 286 machine
  23.  * expanded memory is usually faster, since extended memory access involves
  24.  * an expensive protected-mode-and-back switch.  On 386 and better, extended
  25.  * memory is usually faster.  As distributed, the code tries extended memory
  26.  * first (what? not everyone has a 386? :-).
  27.  *
  28.  * You can disable use of extended/expanded memory entirely by altering these
  29.  * definitions or overriding them from the Makefile (eg, -DEMS_SUPPORTED=0).
  30.  */
  31.  
  32. #ifndef XMS_SUPPORTED
  33. #define XMS_SUPPORTED  1
  34. #endif
  35. #ifndef EMS_SUPPORTED
  36. #define EMS_SUPPORTED  1
  37. #endif
  38.  
  39.  
  40. #include "jinclude.h"
  41. #include "jmemsys.h"
  42.  
  43. #ifdef INCLUDES_ARE_ANSI
  44. #include <stdlib.h>        /* to declare malloc(), free(), getenv() */
  45. #else
  46. extern void * malloc PP((size_t size));
  47. extern void free PP((void *ptr));
  48. extern char * getenv PP((const char * name));
  49. #endif
  50.  
  51. #ifdef NEED_FAR_POINTERS
  52.  
  53. #ifdef __TURBOC__
  54. /* These definitions work for Borland C (Turbo C) */
  55. #include <alloc.h>        /* need farmalloc(), farfree() */
  56. #define far_malloc(x)    farmalloc(x)
  57. #define far_free(x)    farfree(x)
  58. #else
  59. /* These definitions work for Microsoft C and compatible compilers */
  60. #include <malloc.h>        /* need _fmalloc(), _ffree() */
  61. #define far_malloc(x)    _fmalloc(x)
  62. #define far_free(x)    _ffree(x)
  63. #endif
  64.  
  65. #endif
  66.  
  67. #ifdef DONT_USE_B_MODE        /* define mode parameters for fopen() */
  68. #define READ_BINARY    "r"
  69. #else
  70. #define READ_BINARY    "rb"
  71. #endif
  72.  
  73.  
  74. /*
  75.  * Declarations for assembly-language support routines (see jmemdosa.asm).
  76.  *
  77.  * The functions are declared "far" as are all pointer arguments;
  78.  * this ensures the assembly source code will work regardless of the
  79.  * compiler memory model.  We assume "short" is 16 bits, "long" is 32.
  80.  */
  81.  
  82. typedef void far * XMSDRIVER;    /* actually a pointer to code */
  83. typedef struct {        /* registers for calling XMS driver */
  84.     unsigned short ax, dx, bx;
  85.     void far * ds_si;
  86.       } XMScontext;
  87. typedef struct {        /* registers for calling EMS driver */
  88.     unsigned short ax, dx, bx;
  89.     void far * ds_si;
  90.       } EMScontext;
  91.  
  92. EXTERN short far jdos_open PP((short far * handle, char far * filename));
  93. EXTERN short far jdos_close PP((short handle));
  94. EXTERN short far jdos_seek PP((short handle, long offset));
  95. EXTERN short far jdos_read PP((short handle, void far * buffer,
  96.                    unsigned short count));
  97. EXTERN short far jdos_write PP((short handle, void far * buffer,
  98.                 unsigned short count));
  99. EXTERN void far jxms_getdriver PP((XMSDRIVER far *));
  100. EXTERN void far jxms_calldriver PP((XMSDRIVER, XMScontext far *));
  101. EXTERN short far jems_available PP((void));
  102. EXTERN void far jems_calldriver PP((EMScontext far *));
  103.  
  104.  
  105. static external_methods_ptr methods; /* saved for access to error_exit */
  106.  
  107. static long total_used;        /* total FAR memory requested so far */
  108.  
  109.  
  110. /*
  111.  * Selection of a file name for a temporary file.
  112.  * This is highly system-dependent, and you may want to customize it.
  113.  */
  114.  
  115. static int next_file_num;    /* to distinguish among several temp files */
  116.  
  117. LOCAL void
  118. select_file_name (char * fname)
  119. {
  120.   const char * env;
  121.   char * ptr;
  122.   FILE * tfile;
  123.  
  124.   /* Keep generating file names till we find one that's not in use */
  125.   for (;;) {
  126.     /* Get temp directory name from environment TMP or TEMP variable;
  127.      * if none, use "."
  128.      */
  129.     if ((env = (const char *) getenv("TMP")) == NULL)
  130.       if ((env = (const char *) getenv("TEMP")) == NULL)
  131.     env = ".";
  132.     if (*env == '\0')        /* null string means "." */
  133.       env = ".";
  134.     ptr = fname;        /* copy name to fname */
  135.     while (*env != '\0')
  136.       *ptr++ = *env++;
  137.     if (ptr[-1] != '\\' && ptr[-1] != '/')
  138.       *ptr++ = '\\';        /* append backslash if not in env variable */
  139.     /* Append a suitable file name */
  140.     next_file_num++;        /* advance counter */
  141.     sprintf(ptr, "JPG%03d.TMP", next_file_num);
  142.     /* Probe to see if file name is already in use */
  143.     if ((tfile = fopen(fname, READ_BINARY)) == NULL)
  144.       break;
  145.     fclose(tfile);        /* oops, it's there; close tfile & try again */
  146.   }
  147. }
  148.  
  149.  
  150. /*
  151.  * Near-memory allocation and freeing are controlled by the regular library
  152.  * routines malloc() and free().
  153.  */
  154.  
  155. GLOBAL void *
  156. jget_small (size_t sizeofobject)
  157. {
  158.   /* near data space is NOT counted in total_used */
  159. #ifndef NEED_FAR_POINTERS
  160.   total_used += sizeofobject;
  161. #endif
  162.   return (void *) malloc(sizeofobject);
  163. }
  164.  
  165. GLOBAL void
  166. jfree_small (void * object)
  167. {
  168.   free(object);
  169. }
  170.  
  171.  
  172. /*
  173.  * Far-memory allocation and freeing
  174.  */
  175.  
  176. #ifdef NEED_FAR_POINTERS
  177.  
  178. GLOBAL void FAR *
  179. jget_large (size_t sizeofobject)
  180. {
  181.   total_used += sizeofobject;
  182.   return (void FAR *) far_malloc(sizeofobject);
  183. }
  184.  
  185. GLOBAL void
  186. jfree_large (void FAR * object)
  187. {
  188.   far_free(object);
  189. }
  190.  
  191. #endif
  192.  
  193.  
  194. /*
  195.  * This routine computes the total memory space available for allocation.
  196.  * It's impossible to do this in a portable way; our current solution is
  197.  * to make the user tell us (with a default value set at compile time).
  198.  * If you can actually get the available space, it's a good idea to subtract
  199.  * a slop factor of 5% or so.
  200.  */
  201.  
  202. #ifndef DEFAULT_MAX_MEM        /* so can override from makefile */
  203. #define DEFAULT_MAX_MEM        300000L /* for total usage about 450K */
  204. #endif
  205.  
  206. GLOBAL long
  207. jmem_available (long min_bytes_needed, long max_bytes_needed)
  208. {
  209.   return methods->max_memory_to_use - total_used;
  210. }
  211.  
  212.  
  213. /*
  214.  * Backing store (temporary file) management.
  215.  * Backing store objects are only used when the value returned by
  216.  * jmem_available is less than the total space needed.  You can dispense
  217.  * with these routines if you have plenty of virtual memory; see jmemnobs.c.
  218.  */
  219.  
  220. /*
  221.  * For MS-DOS we support three types of backing storage:
  222.  *   1. Conventional DOS files.  We access these by direct DOS calls rather
  223.  *      than via the stdio package.  This provides a bit better performance,
  224.  *      but the real reason is that the buffers to be read or written are FAR.
  225.  *      The stdio library for small-data memory models can't cope with that.
  226.  *   2. Extended memory, accessed per the XMS V2.0 specification.
  227.  *   3. Expanded memory, accessed per the LIM/EMS 4.0 specification.
  228.  * You'll need copies of those specs to make sense of the related code.
  229.  * The specs are available by Internet FTP from SIMTEL20 and its various
  230.  * mirror sites; see microsoft/xms20.arc and info/limems41.zip.
  231.  */
  232.  
  233.  
  234. /*
  235.  * Access methods for a DOS file.
  236.  */
  237.  
  238.  
  239. METHODDEF void
  240. read_file_store (backing_store_ptr info, void FAR * buffer_address,
  241.          long file_offset, long byte_count)
  242. {
  243.   if (jdos_seek(info->handle.file_handle, file_offset))
  244.     ERREXIT(methods, "seek failed on temporary file");
  245.   /* Since MAX_ALLOC_CHUNK is less than 64K, byte_count will be too. */
  246.   if (byte_count > 65535L)    /* safety check */
  247.     ERREXIT(methods, "MAX_ALLOC_CHUNK should be less than 64K");
  248.   if (jdos_read(info->handle.file_handle, buffer_address,
  249.         (unsigned short) byte_count))
  250.     ERREXIT(methods, "read failed on temporary file");
  251. }
  252.  
  253.  
  254. METHODDEF void
  255. write_file_store (backing_store_ptr info, void FAR * buffer_address,
  256.           long file_offset, long byte_count)
  257. {
  258.   if (jdos_seek(info->handle.file_handle, file_offset))
  259.     ERREXIT(methods, "seek failed on temporary file");
  260.   /* Since MAX_ALLOC_CHUNK is less than 64K, byte_count will be too. */
  261.   if (byte_count > 65535L)    /* safety check */
  262.     ERREXIT(methods, "MAX_ALLOC_CHUNK should be less than 64K");
  263.   if (jdos_write(info->handle.file_handle, buffer_address,
  264.          (unsigned short) byte_count))
  265.     ERREXIT(methods, "write failed on temporary file --- out of disk space?");
  266. }
  267.  
  268.  
  269. METHODDEF void
  270. close_file_store (backing_store_ptr info)
  271. {
  272.   jdos_close(info->handle.file_handle);    /* close the file */
  273.   remove(info->temp_name);    /* delete the file */
  274. /* If your system doesn't have remove(), try unlink() instead.
  275.  * remove() is the ANSI-standard name for this function, but
  276.  * unlink() was more common in pre-ANSI systems.
  277.  */
  278.   TRACEMS1(methods, 1, "Closed DOS file %d", info->handle.file_handle);
  279. }
  280.  
  281.  
  282. LOCAL boolean
  283. open_file_store (backing_store_ptr info, long total_bytes_needed)
  284. {
  285.   short handle;
  286.   char tracemsg[TEMP_NAME_LENGTH+40];
  287.  
  288.   select_file_name(info->temp_name);
  289.   if (jdos_open((short far *) & handle, (char far *) info->temp_name)) {
  290.     /* hack to get around TRACEMS' inability to handle string parameters */
  291.     sprintf(tracemsg, "Failed to create temporary file %s", info->temp_name);
  292.     ERREXIT(methods, tracemsg);    /* jopen_backing_store will fail anyway */
  293.     return FALSE;
  294.   }
  295.   info->handle.file_handle = handle;
  296.   info->read_backing_store = read_file_store;
  297.   info->write_backing_store = write_file_store;
  298.   info->close_backing_store = close_file_store;
  299.   /* hack to get around TRACEMS' inability to handle string parameters */
  300.   sprintf(tracemsg, "Opened DOS file %d  %s", handle, info->temp_name);
  301.   TRACEMS(methods, 1, tracemsg);
  302.   return TRUE;            /* succeeded */
  303. }
  304.  
  305.  
  306. /*
  307.  * Access methods for extended memory.
  308.  */
  309.  
  310. #if XMS_SUPPORTED
  311.  
  312. static XMSDRIVER xms_driver;    /* saved address of XMS driver */
  313.  
  314. typedef union {            /* either long offset or real-mode pointer */
  315.     long offset;
  316.     void far * ptr;
  317.       } XMSPTR;
  318.  
  319. typedef struct {        /* XMS move specification structure */
  320.     long length;
  321.     XMSH src_handle;
  322.     XMSPTR src;
  323.     XMSH dst_handle;
  324.     XMSPTR dst;
  325.       } XMSspec;
  326.  
  327. #define ODD(X)    (((X) & 1L) != 0)
  328.  
  329.  
  330. METHODDEF void
  331. read_xms_store (backing_store_ptr info, void FAR * buffer_address,
  332.         long file_offset, long byte_count)
  333. {
  334.   XMScontext ctx;
  335.   XMSspec spec;
  336.   char endbuffer[2];
  337.  
  338.   /* The XMS driver can't cope with an odd length, so handle the last byte
  339.    * specially if byte_count is odd.  We don't expect this to be common.
  340.    */
  341.  
  342.   spec.length = byte_count & (~ 1L);
  343.   spec.src_handle = info->handle.xms_handle;
  344.   spec.src.offset = file_offset;
  345.   spec.dst_handle = 0;
  346.   spec.dst.ptr = buffer_address;
  347.   
  348.   ctx.ds_si = (void far *) & spec;
  349.   ctx.ax = 0x0b00;        /* EMB move */
  350.   jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
  351.   if (ctx.ax != 1)
  352.     ERREXIT(methods, "read from extended memory failed");
  353.  
  354.   if (ODD(byte_count)) {
  355.     read_xms_store(info, (void FAR *) endbuffer,
  356.            file_offset + byte_count - 1L, 2L);
  357.     ((char FAR *) buffer_address)[byte_count - 1L] = endbuffer[0];
  358.   }
  359. }
  360.  
  361.  
  362. METHODDEF void
  363. write_xms_store (backing_store_ptr info, void FAR * buffer_address,
  364.          long file_offset, long byte_count)
  365. {
  366.   XMScontext ctx;
  367.   XMSspec spec;
  368.   char endbuffer[2];
  369.  
  370.   /* The XMS driver can't cope with an odd length, so handle the last byte
  371.    * specially if byte_count is odd.  We don't expect this to be common.
  372.    */
  373.  
  374.   spec.length = byte_count & (~ 1L);
  375.   spec.src_handle = 0;
  376.   spec.src.ptr = buffer_address;
  377.   spec.dst_handle = info->handle.xms_handle;
  378.   spec.dst.offset = file_offset;
  379.  
  380.   ctx.ds_si = (void far *) & spec;
  381.   ctx.ax = 0x0b00;        /* EMB move */
  382.   jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
  383.   if (ctx.ax != 1)
  384.     ERREXIT(methods, "write to extended memory failed");
  385.  
  386.   if (ODD(byte_count)) {
  387.     read_xms_store(info, (void FAR *) endbuffer,
  388.            file_offset + byte_count - 1L, 2L);
  389.     endbuffer[0] = ((char FAR *) buffer_address)[byte_count - 1L];
  390.     write_xms_store(info, (void FAR *) endbuffer,
  391.             file_offset + byte_count - 1L, 2L);
  392.   }
  393. }
  394.  
  395.  
  396. METHODDEF void
  397. close_xms_store (backing_store_ptr info)
  398. {
  399.   XMScontext ctx;
  400.  
  401.   ctx.dx = info->handle.xms_handle;
  402.   ctx.ax = 0x0a00;
  403.   jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
  404.   TRACEMS1(methods, 1, "Freed XMS handle %u", info->handle.xms_handle);
  405.   /* we ignore any error return from the driver */
  406. }
  407.  
  408.  
  409. LOCAL boolean
  410. open_xms_store (backing_store_ptr info, long total_bytes_needed)
  411. {
  412.   XMScontext ctx;
  413.  
  414.   /* Get address of XMS driver */
  415.   jxms_getdriver((XMSDRIVER far *) & xms_driver);
  416.   if (xms_driver == NULL)
  417.     return FALSE;        /* no driver to be had */
  418.  
  419.   /* Get version number, must be >= 2.00 */
  420.   ctx.ax = 0x0000;
  421.   jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
  422.   if (ctx.ax < (unsigned short) 0x0200)
  423.     return FALSE;
  424.  
  425.   /* Try to get space (expressed in kilobytes) */
  426.   ctx.dx = (unsigned short) ((total_bytes_needed + 1023L) >> 10);
  427.   ctx.ax = 0x0900;
  428.   jxms_calldriver(xms_driver, (XMScontext far *) & ctx);
  429.   if (ctx.ax != 1)
  430.     return FALSE;
  431.  
  432.   /* Succeeded, save the handle and away we go */
  433.   info->handle.xms_handle = ctx.dx;
  434.   info->read_backing_store = read_xms_store;
  435.   info->write_backing_store = write_xms_store;
  436.   info->close_backing_store = close_xms_store;
  437.   TRACEMS1(methods, 1, "Obtained XMS handle %u", ctx.dx);
  438.   return TRUE;            /* succeeded */
  439. }
  440.  
  441. #endif /* XMS_SUPPORTED */
  442.  
  443.  
  444. /*
  445.  * Access methods for expanded memory.
  446.  */
  447.  
  448. #if EMS_SUPPORTED
  449.  
  450. typedef union {            /* either offset/page or real-mode pointer */
  451.     struct { unsigned short offset, page; } ems;
  452.     void far * ptr;
  453.       } EMSPTR;
  454.  
  455. typedef struct {        /* EMS move specification structure */
  456.     long length;
  457.     char src_type;        /* 1 = EMS, 0 = conventional memory */
  458.     EMSH src_handle;    /* use 0 if conventional memory */
  459.     EMSPTR src;
  460.     char dst_type;
  461.     EMSH dst_handle;
  462.     EMSPTR dst;
  463.       } EMSspec;
  464.  
  465. #define EMSPAGESIZE    16384L    /* gospel, see the EMS specs */
  466.  
  467. #define HIBYTE(W)  (((W) >> 8) & 0xFF)
  468. #define LOBYTE(W)  ((W) & 0xFF)
  469.  
  470.  
  471. METHODDEF void
  472. read_ems_store (backing_store_ptr info, void FAR * buffer_address,
  473.         long file_offset, long byte_count)
  474. {
  475.   EMScontext ctx;
  476.   EMSspec spec;
  477.  
  478.   spec.length = byte_count;
  479.   spec.src_type = 1;
  480.   spec.src_handle = info->handle.ems_handle;
  481.   spec.src.ems.page = (unsigned short) (file_offset / EMSPAGESIZE);
  482.   spec.src.ems.offset = (unsigned short) (file_offset % EMSPAGESIZE);
  483.   spec.dst_type = 0;
  484.   spec.dst_handle = 0;
  485.   spec.dst.ptr = buffer_address;
  486.   
  487.   ctx.ds_si = (void far *) & spec;
  488.   ctx.ax = 0x5700;        /* move memory region */
  489.   jems_calldriver((EMScontext far *) & ctx);
  490.   if (HIBYTE(ctx.ax) != 0)
  491.     ERREXIT(methods, "read from expanded memory failed");
  492. }
  493.  
  494.  
  495. METHODDEF void
  496. write_ems_store (backing_store_ptr info, void FAR * buffer_address,
  497.          long file_offset, long byte_count)
  498. {
  499.   EMScontext ctx;
  500.   EMSspec spec;
  501.  
  502.   spec.length = byte_count;
  503.   spec.src_type = 0;
  504.   spec.src_handle = 0;
  505.   spec.src.ptr = buffer_address;
  506.   spec.dst_type = 1;
  507.   spec.dst_handle = info->handle.ems_handle;
  508.   spec.dst.ems.page = (unsigned short) (file_offset / EMSPAGESIZE);
  509.   spec.dst.ems.offset = (unsigned short) (file_offset % EMSPAGESIZE);
  510.   
  511.   ctx.ds_si = (void far *) & spec;
  512.   ctx.ax = 0x5700;        /* move memory region */
  513.   jems_calldriver((EMScontext far *) & ctx);
  514.   if (HIBYTE(ctx.ax) != 0)
  515.     ERREXIT(methods, "write to expanded memory failed");
  516. }
  517.  
  518.  
  519. METHODDEF void
  520. close_ems_store (backing_store_ptr info)
  521. {
  522.   EMScontext ctx;
  523.  
  524.   ctx.ax = 0x4500;
  525.   ctx.dx = info->handle.ems_handle;
  526.   jems_calldriver((EMScontext far *) & ctx);
  527.   TRACEMS1(methods, 1, "Freed EMS handle %u", info->handle.ems_handle);
  528.   /* we ignore any error return from the driver */
  529. }
  530.  
  531.  
  532. LOCAL boolean
  533. open_ems_store (backing_store_ptr info, long total_bytes_needed)
  534. {
  535.   EMScontext ctx;
  536.  
  537.   /* Is EMS driver there? */
  538.   if (! jems_available())
  539.     return FALSE;
  540.  
  541.   /* Get status, make sure EMS is OK */
  542.   ctx.ax = 0x4000;
  543.   jems_calldriver((EMScontext far *) & ctx);
  544.   if (HIBYTE(ctx.ax) != 0)
  545.     return FALSE;
  546.  
  547.   /* Get version, must be >= 4.0 */
  548.   ctx.ax = 0x4600;
  549.   jems_calldriver((EMScontext far *) & ctx);
  550.   if (HIBYTE(ctx.ax) != 0 || LOBYTE(ctx.ax) < 0x40)
  551.     return FALSE;
  552.  
  553.   /* Try to allocate requested space */
  554.   ctx.ax = 0x4300;
  555.   ctx.bx = (unsigned short) ((total_bytes_needed + EMSPAGESIZE-1L) / EMSPAGESIZE);
  556.   jems_calldriver((EMScontext far *) & ctx);
  557.   if (HIBYTE(ctx.ax) != 0)
  558.     return FALSE;
  559.  
  560.   /* Succeeded, save the handle and away we go */
  561.   info->handle.ems_handle = ctx.dx;
  562.   info->read_backing_store = read_ems_store;
  563.   info->write_backing_store = write_ems_store;
  564.   info->close_backing_store = close_ems_store;
  565.   TRACEMS1(methods, 1, "Obtained EMS handle %u", ctx.dx);
  566.   return TRUE;            /* succeeded */
  567. }
  568.  
  569. #endif /* EMS_SUPPORTED */
  570.  
  571.  
  572. /*
  573.  * Initial opening of a backing-store object.
  574.  */
  575.  
  576. GLOBAL void
  577. jopen_backing_store (backing_store_ptr info, long total_bytes_needed)
  578. {
  579.   /* Try extended memory, then expanded memory, then regular file. */
  580. #if XMS_SUPPORTED
  581.   if (open_xms_store(info, total_bytes_needed))
  582.     return;
  583. #endif
  584. #if EMS_SUPPORTED
  585.   if (open_ems_store(info, total_bytes_needed))
  586.     return;
  587. #endif
  588.   if (open_file_store(info, total_bytes_needed))
  589.     return;
  590.   ERREXIT(methods, "Failed to create temporary file");
  591. }
  592.  
  593.  
  594. /*
  595.  * These routines take care of any system-dependent initialization and
  596.  * cleanup required.  Keep in mind that jmem_term may be called more than
  597.  * once.
  598.  */
  599.  
  600. GLOBAL void
  601. jmem_init (external_methods_ptr emethods)
  602. {
  603.   methods = emethods;        /* save struct addr for error exit access */
  604.   emethods->max_memory_to_use = DEFAULT_MAX_MEM;
  605.   total_used = 0;
  606.   next_file_num = 0;
  607. }
  608.  
  609. GLOBAL void
  610. jmem_term (void)
  611. {
  612.   /* no work */
  613. }
  614.